#include <QtCore>
#include <QtNetwork>
#include <QtSql>
#include <QCryptographicHash>
#include <iostream>
#include "spythread.h"

using namespace std;

SpyThread::SpyThread(int socketDescriptor)
    : QThread()
{
    //emit log(5,"constructor");
    auth = false;
    admin = false;
    sockDesc = socketDescriptor;
    //QString status = "socket descriptor: " + QString::number(sockDesc);
    emit log(5,"constructor complete");
}

SpyThread::~SpyThread()
{
    db.close();
    sdLog(4,"~SpyThread: database closed");
}

void SpyThread::sdLog(int lvl, QString msg)
{
    QString status = "sd " + QString::number(sockDesc) + ": " + msg;
    emit log(lvl,status);
}

bool SpyThread::connectToDb()
{
    dbId = QString::number(rand());
    db = QSqlDatabase::addDatabase("QMYSQL", dbId);
    db.setHostName("127.0.0.1");
    db.setDatabaseName("spydb");
    db.setUserName("spy");
    db.setPassword("P@ssw0rd");

    QString status;
    if (db.open()) {
        status = "db id: " + dbId;
        sdLog(5,status);
        return true;
    } else {
        status = "FAILED TO OPEN DB with id " + dbId;
        sdLog(1,status);
    }
    return false;
}

bool SpyThread::isAuth(QString name, QString pass)
{
    pass = pass + name;
    QString query = "SELECT id FROM users WHERE name='" + name + "' AND password=sha1('" + pass + "');";
    sdLog(6,query);
    QSqlQuery dbResult = db.exec(query);
    if (dbResult.size() == 1) {
        dbResult.next();
        user_id = dbResult.value(0).toInt();
        return true;
    }
    return false;
}

bool SpyThread::isAdmin(QString user)
{
    QString query = "SELECT id FROM users WHERE id='" + QString::number(user_id) + "' AND admin=true;";
    sdLog(6,query);
    //QString status = user + " authorized as ";
    if (db.exec(query).size() == 1) {
        //status += "admin";
        //sdLog(4,status);
        return true;
    }
    //status += "normal user";
    //sdLog(5,status);
    return false;
}

QString SpyThread::getAllMessages()
{
    QString query = "SELECT users.name,messages.msg_time,messages.message ";
    query += "FROM messages,users ";
    query += "WHERE ((user_id in (SELECT author_id FROM acl WHERE viewer_id=";
    query += QString::number(user_id) + ")) AND (users.id=messages.user_id));";
    sdLog(6,query);
    QSqlQuery dbResult = db.exec(query);
    QString status = "dbResult.size() = " + QString::number(dbResult.size());
    sdLog(7,status);

    QString msg, msgList;
    while (dbResult.next()) {
        msg = dbResult.value(0).toString() + "__";
        msg += dbResult.value(1).toString() + "__";
        msg += dbResult.value(2).toString() + "&&";
        sdLog(7,msg);
        msgList += msg;
    }
    return msgList;
}

QString SpyThread::getAllAcls()
{
    // get list of all user ids
    QString query = "SELECT id,name FROM users";
    sdLog(6,query);
    QSqlQuery userResult = db.exec(query);
    QString status = "userResult.size() = " + QString::number(userResult.size());
    sdLog(7,status);
    //QString userList;
    QString aclList;
    while (userResult.next()) {
        aclList += "&&" + userResult.value(1).toString() + ":"; // add viewer to list

        QString aclQuery = "SELECT name FROM users WHERE id IN ";
        aclQuery += "(SELECT author_id FROM acl WHERE viewer_id=";
        aclQuery += userResult.value(0).toString() + ");";
        sdLog(6,aclQuery);
        QSqlQuery aclResult = db.exec(aclQuery);
        status = "aclResult.size() = " + QString::number(aclResult.size());
        sdLog(7,status);
        while (aclResult.next()) {
            aclList += aclResult.value(0).toString() + ",";
        }
        aclList.chop(1); // remove trailing comma
    }

    sdLog(5,aclList);
    return aclList;
}

QString SpyThread::getUserId(QString name)
{
    QString query = "SELECT id FROM users WHERE name='" + name + "';";
    sdLog(6,query);
    QSqlQuery dbResult = db.exec(query);
    dbResult.next();
    return(dbResult.value(0).toString());
}

void SpyThread::addMsg(QString message)
{
    QSqlQuery *query = new QSqlQuery(db);
    query->prepare("INSERT INTO messages (user_id, message) "
        "VALUES (:user_id, :message)");
    query->bindValue(":user_id", QString::number(user_id));
    query->bindValue(":message", message);
    query->exec();
    QString status = "query.size() = " + QString::number(query->size());
    sdLog(7,status);

    return;
}

void SpyThread::addAcl(QString viewer, QString author)
{
    //QString status = "addAcl(" + viewer + "," + author + ")";
    QString viewer_id = getUserId(viewer);
    QString author_id = getUserId(author);
    QString status = "addAcl(" + viewer + " [" + viewer_id + "], "
            + author + " [" + author_id + "]" + ") submitted by " + user;
    sdLog(4,status);

    QSqlQuery *query = new QSqlQuery(db);
    query->prepare("INSERT INTO acl (viewer_id,author_id) "
        "VALUES (:viewer_id, :author_id)");
    query->bindValue(":viewer_id", viewer_id);
    query->bindValue(":author_id", author_id);
    query->exec();
    status = query->lastQuery();
    sdLog(6,status);
    status = "query.size() = " + QString::number(query->size());
    sdLog(7,status);

    return;
}

void SpyThread::delAcl(QString viewer, QString author)
{
    QString status = "delAcl(" + viewer + ", " + author + ") "
            + " submitted by " + user;
    sdLog(4,status);
    /**/
    QString vQuery = "SELECT id FROM users WHERE name='" + viewer + "';";
    QString aQuery = "SELECT id FROM users WHERE name='" + author + "';";
    QSqlQuery vResult = db.exec(vQuery);
    QSqlQuery aResult = db.exec(aQuery);
    vResult.next();
    aResult.next();
    /**/
    QString query = "SELECT id FROM acl WHERE (viewer_id=";
    query += vResult.value(0).toString() + " AND author_id=";
    query += aResult.value(0).toString() + ");";
    //QString query = "SELECT id FROM acl WHERE (viewer_id=" + viewer + " AND author_id=" + author + ");";
    sdLog(6,query);
    QSqlQuery dbResult = db.exec(query);
    dbResult.next();
    query = "DELETE FROM acl WHERE id=" + dbResult.value(0).toString() + ";";
    sdLog(6,query);
    dbResult = db.exec(query);
    return;
}

void SpyThread::clrAcl(QString viewer)
{
    QString status = "clearing ACL for " + viewer;
    sdLog(4,status);
    QString query = "SELECT id FROM acl WHERE viewer_id=" + viewer + ";";
    sdLog(6,query);
    QSqlQuery dbResult = db.exec(query);
    for (int i = 0; i < dbResult.size(); ++i) {
        dbResult.next();
        query = "DELETE FROM acl WHERE id=" + dbResult.value(0).toString() + ";";
        sdLog(6,query);
        QSqlQuery rmQuery = db.exec(query);
    }
    return;
}

void SpyThread::addUsr(QString name, QString pass)
{
    // add to user table
    pass = pass + name;
    QString pwHash = QCryptographicHash::hash(pass.toUtf8(), QCryptographicHash::Sha1).toHex();
    QSqlQuery *query = new QSqlQuery(db);
    query->prepare("INSERT INTO users (name,password) VALUES (:name, :password)");
    query->bindValue(":name", name);
    query->bindValue(":password", pwHash);
    query->exec();
    QString status = query->lastQuery();
    sdLog(6,status);
    status = "query.size() = " + QString::number(query->size());
    sdLog(7,status);
    // add to acl table
    QString uid = getUserId(name);
    query->prepare("INSERT INTO acl (author_id,viewer_id) VALUES (:aid, :vid)");
    query->bindValue(":aid", uid);
    query->bindValue(":vid", uid);
    query->exec();
    status = query->lastQuery();
    sdLog(6,status);

    return;
}

void SpyThread::delUsr(QString name)
{
    if (name != user) {
        // remove user - begin
        QString status = "deleting user " + name + "...";
        sdLog(5,status);
        // remove from user table
        QString query = "SELECT id FROM users WHERE name='" + name + "';";
        sdLog(6,query);
        QSqlQuery dbResult = db.exec(query);
        dbResult.next();
        QString uid = dbResult.value(0).toString();
        query = "DELETE FROM users WHERE id=" + uid + ";";
        sdLog(6,query);
        dbResult = db.exec(query);
        status = "Deleted "  + name + " from users table";
        sdLog(5,status);
        // remove from messages table
        query = "DELETE FROM messages WHERE user_id=" + uid + ";";
        sdLog(6,query);
        dbResult = db.exec(query);
        status = "Deleted messages from author " + name;
        sdLog(5,status);
        // remove from acl table
        query = "DELETE FROM acl WHERE (viewer_id=" + uid + " OR author_id=" + uid + ");";
        sdLog(6,query);
        dbResult = db.exec(query);
        status = "Deleted ACL entries with author or viewer " + name;
        sdLog(5,status);
        // remove user - complete
        status = "Removed all database entries for user " + name;
        sdLog(4,status);
    }
    return;
}

void SpyThread::run()
{
    blocksize = 0;
    QString status = "run(): blocksize " + QString::number(blocksize);
    sdLog(5,status);

    sslSock = new QSslSocket;
    sdLog(5,"run(): SSL socket created");

    // read local certificate and private key (generated w/openssl)
    sslSock->setLocalCertificate("cacert.pem");
    sslSock->setPrivateKey("privkey.pem");

    // check that the certificate & private key are not null
    if (sslSock->localCertificate().isNull()) {
        sdLog(2,"run(): SSL certificate is NULL!");
    } else {
        sdLog(4,"run(): SSL certificate read");
    }
    if (sslSock->privateKey().isNull()) {
        sdLog(2,"run(): SSL private key is NULL!");
    } else {
        sdLog(4,"run(): SSL private key read");
    }

    // associate socketDescriptor with new SSL socket
     if (sslSock->setSocketDescriptor(sockDesc))
     {
         connect(sslSock, SIGNAL(readyRead()), this, SLOT(readyRead()),Qt::DirectConnection);
         sslSock->startServerEncryption();
         sdLog(4,"run(): SSL socket encryption started");
     } else {
         emit error(sslSock->error());
         sdLog(2,"run(): SSL socket descriptor could NOT be set!");
         return;
     }
    connect(sslSock,SIGNAL(disconnected()),this,SLOT(disconnect()),Qt::DirectConnection);
    sdLog(5,"run(): SSL disconnect signals are connected");

    exec();
    sdLog(5,"run(): started thread event loop");
}

void SpyThread::readyRead()
{
    QString status = "readyRead(): original blocksize " + QString::number(blocksize);
    sdLog(5,status);

    QDataStream in(sslSock);
    in.setVersion(QDataStream::Qt_4_0);
    sdLog(4,"readyRead(): created data stream from SSL socket");

    if (blocksize == 0) {
        if (sslSock->bytesAvailable() < (int)sizeof(quint16)) {
            sdLog(5,"readyRead(): sslSock->bytesAvailable() < (int)sizeof(quint64)");
            return;
        }
        in >> blocksize;
    }
    status = "readyRead(): blocksize " + QString::number(blocksize);
    sdLog(5,status);

    status = "readyRead(): SSL socket bytes available " + QString::number(sslSock->bytesAvailable());
    status += ", blocksize " + QString::number(blocksize);
    sdLog(5,status);

    if (sslSock->bytesAvailable() < blocksize)
    {
        status = "readyRead(): error: bytes available " + QString::number(sslSock->bytesAvailable());
        status += ", blocksize " + QString::number(blocksize);
        sdLog(2,status);
        return;
    }

    sdLog(4,"readyRead(): recieved full msg");

    QString msg;
    in >> msg;
    text = msg;

    status = "readyRead(): text [" + text + "]";
    sdLog(5,status);

    blocksize = 0;

    sdLog(5,"readyRead(): parsing text for user & pass");
    QStringList textSplit = text.split("::");
    if (textSplit.size() < 4) {
        sdLog(3,"readyRead(): invalid message format, not enough parameters");
        emit finished();
        return;
    } else {
        user = textSplit[0];
        pass = textSplit[1];
        tbl = textSplit[2];
        status = "readyRead(): user " + user + ", pass " + pass + ", tbl " + tbl;
        sdLog(4,status);
        // build operation string
        textSplit.removeFirst();
        textSplit.removeFirst();
        textSplit.removeFirst();
        opt = textSplit.at(0).toLocal8Bit().constData();
        for (int i = 1; i < textSplit.size(); ++i) {
            opt += "::";
            opt += textSplit.at(i).toLocal8Bit().constData();
        }
        status = "readyRead(): opt contains " + QString::number(textSplit.size()) + " parts";
        sdLog(4,status);
    }

    if (!auth) {
        sdLog(3,"readyRead(): checking for login authorization...");
        if (connectToDb()) {
            status = "connected db: " + db.userName() + "@" + db.hostName();
            status += ":" + db.databaseName();
            sdLog(4,status);
        } else {
            status = "failed to open db, error: " + db.lastError().databaseText();
            sdLog(2,status);
            emit finished();
        }

        if (isAuth(user,pass)) {
            auth = true;
            if (isAdmin(user)) {
                admin = true;
                status = user + " logged in as admin";
                sdLog(4,status);
            } else {
                status = user + " logged in as normal user";
                sdLog(4,status);
            }
        } else {
            status = user + " NOT AUTHORIZED for login!";
            sdLog(3,status);
            response = "0";
            sdLog(5,"sending 'invalid credentials' response");
            sendData(response);
            disconnect();
            return;
        }
        response = "1::" + QString(admin?"1":"0");
        sdLog(5,"readyRead(): started building response");
        response += "::"+ getAllMessages();
        sdLog(5,"readyRead(): added all messages to response");
        if (admin) {
            response += "::" + getAllAcls();
            sdLog(5,"readyRead(): added all ACLs to response");
        }
        sendData(response);
    } else {
        response = "1::" + QString(admin?"1":"0");
        sdLog(5,"readyRead(): started building response");
        if (tbl == "msg") {
            status = "splitting opt: " + opt;
            sdLog(5,status);
            QStringList optSplit = opt.split("::");
            if (optSplit[0] == "new") {
                if (optSplit.size() > 2) {
                    status = "msg new: TOO MANY options (" + QString::number(optSplit.size()) + ")";
                    sdLog(3,status);
                } else {
                    status = "adding new message: " + optSplit[1];
                    sdLog(4,status);
                    addMsg(optSplit[1]);
                }
            } else if (optSplit[0] == "get") {
                if (optSplit.size() > 1) {
                    status = "msg get: TOO MANY options (" + QString::number(optSplit.size()) + ")";
                    sdLog(3,status);
                }
            } else {
                status = "msg: INVALID COMMAND (" + optSplit[0] + ")";
                sdLog(3,status);
            }
        } else if (tbl == "usr") {
            if (!admin) {
                sdLog(3,"normal user attempting to access ADMIN table");
            } else {
                QStringList optSplit = opt.split("::");
                if (optSplit[0] == "add") {
                    if (optSplit.size() != 3) {
                        status = "usr add: INVALID NUMBER of options (" + QString::number(optSplit.size()) + ")";
                        sdLog(3,status);
                    } else {
                        status = "adding new user: " + optSplit[1];
                        sdLog(4,status);
                        addUsr(optSplit[1],optSplit[2]);
                    }
                } else if (optSplit[0] == "del") {
                    if (optSplit.size() != 2) {
                        sdLog(3,"usr del: INVALID OPTION");
                    } else {
                        status = "deleting user: " + optSplit[1];
                        sdLog(4,status);
                        delUsr(optSplit[1]);
                    }
                } else {
                    status = "usr: INVALID COMMAND (" + optSplit[0] + ")";
                    sdLog(3,status);
                }
            }
        } else if (tbl == "acl") {
            if (!admin) {
                sdLog(3,"normal user attempting to access ADMIN table");
            } else {
                // opt format: viewer:author1,author2,...,authorN
                QStringList optSplit = opt.split(":");
                QString viewer = optSplit[0];
                QString viewer_id = getUserId(viewer);
                QStringList authorList = optSplit[1].split(",");

                clrAcl(viewer_id);

                for (int i = 0; i < (authorList.size() - 1); ++i) {
                    addAcl(viewer, authorList[i]);
                }
                //sdLog(8,status);
            }
        }
        response += "::" + getAllMessages();
        sdLog(5,"readyRead(): added all messages to response");

        if (admin) {
            response += "::" + getAllAcls();
            sdLog(5,"readyRead(): added all ACLs to response");
        }
        sendData(response);
    }
}


void SpyThread::sendData(QString msg)
{
    QString status = "sendData(msg): " + msg;
    sdLog(5,status);

    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_0);
    out << (quint16)0;
    out << msg;
    out.device()->seek(0);
    out << (quint16)(block.size() - sizeof(quint16));

    sslSock->write(block);
    sdLog(4,"thread sent msg to socket");
}


void SpyThread::disconnect()
{
    qDebug() << sockDesc << "disconnecting";
    sslSock->disconnectFromHost();
    this->deleteLater();  // thread no longer needed
}
